//$Id: TextUI.java 250 2010-12-14 16:40:36Z liangyj20030161@yahoo.com.sg $

package builders;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;


/**
 * This is the user interface that is in text. For people who like typing and imagining alot.
 * @author Liang Yujing
 */
public class TextUI {
	Logic logic;
	GameState gameState;	
	boolean endGame = false;
	boolean promptPlayersStatus = true;
	boolean promptSaveGame = true;
	boolean promptRevertSavedGame = true;
	boolean promptDifficulty = true;
	
	private void initialise() {
		logic = new Logic();
		gameState = logic.execute(new Message(GameState.GameMode.NEW_GAME, null));
	}

	private void interactWithUser() {
		printGreeting();
		executeCommand();
		printExitMessage();
	}

	private void printGreeting() {
		System.out.println("Welcome to Builders!");
	}

	/**
	 * @param
	 * @return void
	 * A while loop that makes the game iterate through different game states
	 * Will be terminated when player types in "quit" to quit the game
	 */
	private void executeCommand() {
		while (!endGame) {
			switch (gameState.getGameMode()) {
			case NEW_GAME:
				newGame();
				break;
			case INITIALIZE:
				initialize();
				break;
			case LOAD_GAME:
				loadGame();
				break;
			case ROLL_DIE:
				rollDie();
				break;
			case MOVE_PIECE:
				movePiece();
				break;
			case TILE_RESULTS:
				tileResults();
				break;
			case END_TURN:
				endTurn();
				break;
			case BUILD:
				build();
				break;
			case END_GAME:
				endGame();
				break;
			case SAVE_GAME:
				saveGame();
				break;
			case ERROR:
				error();
				break;
			}
		}
	}

	/**
	 * @param
	 * @return
	 * In newGame(), players are first prompted if they want to revert to a saved game
	 * if yes, saved game is loaded
	 * if no, players will be prompted to enter number of players and player names
	 * At any time, they can enter "quit" to quit the game
	 */
	private void newGame() {
		System.out.print("Do you want to revert to a saved game? (yes/no)");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

		String savedGameName = "";
		try{
			/*
			 * loads the saved game if players wants to and provides the correct file name
			 * if player provides the wrong file name, an error message will be displayed
			 */
			String playerAnswer = in.readLine();
			if (playerAnswer.equals("yes")){
				savedGameName = promptGameName();

				String workingDir = System.getProperty("user.dir");
				String absolutePathOfSaveGame = workingDir + "\\" + savedGameName;
				String[] result = {absolutePathOfSaveGame};
				gameState = logic.execute(new Message(GameState.GameMode.LOAD_GAME, result));
				return;
			}

			if ((!playerAnswer.equals("yes")) && (!playerAnswer.equals("no"))){
				System.out.print("Please enter 'yes' or 'no'.");
				return;
			}

		} catch (Exception e) {
			System.err.println(e);
		} 

		String[] result = null;
		System.out.println("Welcome to a new game.");
		while (promptPlayersStatus && !endGame) {
			result = promptPlayers();
		}
		
		/*
		 * result may be null if player types in quit when entering the players' names
		 * in other words, we do not allow players to have "quit" as their names
		 */
		if (result != null){
			Message message = new Message(GameState.GameMode.INITIALIZE, result);
			gameState = logic.execute(message);
		}
	}

	/**
	 * 
	 * @return 
	 * This is the part of the code that does the *real* work for newGame()
	 * Repeatedly prompts player to enter number of players and player names till valid input is entered
	 * Extensive error checking mechanisms in place (hopefully)
	 */
	private String[] promptPlayers() {
		final int RESULT_SIZE = 9;
		final int MAX_NUMBER_OF_PLAYERS = 4;
		final int DIFFICULTY_INDEX = 8;
		int numberOfPlayers;
		int numberOfHumanPlayers;
		int numberOfAIPlayers;
		int numberOfNullPlayers;
		String[] humanPlayerNames = new String[4];
		String[] result = new String[RESULT_SIZE];
		String levelOfDifficulty = "";

		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		System.out.print("Enter number of players:");
		try {
			String totalPlayers = in.readLine();
			

			/*
			 * checks if player entered "quit" to quit the game
			 */
			if(checkEndGame(totalPlayers)){
				endGame = true;
				return null;
			}

			/*
			 * checks if player entered an integer
			 * if not, prompt player to enter a valid number 
			 */
			if (isInteger(totalPlayers)) {
				numberOfPlayers = Integer.parseInt(totalPlayers);
			} else {
				System.out.println("Please enter a valid number.");
				return null;
			}

			/*
			 * since our game supports a maximum of 4 players, any number beyond 4 is invalid
			 * any number less than 1 is also not valid
			 */
			if (numberOfPlayers > 4 || numberOfPlayers <= 0) {
				System.out.println("Please enter a valid number of players.");
				return null;
			}

			/*
			 * this part allows players to choose number of human players in the game
			 */
			System.out.print("Enter number of human players:");
			String humanPlayers = in.readLine();

			/*
			 * again, checks if player entered "quit" to quit the game
			 */
			if(checkEndGame(humanPlayers)){
				endGame = true;
				return null;
			}

			/*
			 * checks if number entered is an integer
			 * if not, prompt player to enter a valid number
			 */
			if (isInteger(humanPlayers)) {
				numberOfHumanPlayers = Integer.parseInt(humanPlayers);
			} else {
				System.out.println("Please enter a valid number.");
				return null;
			}

			/*
			 * checks if number of human players is less or equal to number of total players
			 * if not, prompt player to enter a valid number 
			 */
			if (numberOfHumanPlayers > numberOfPlayers) {
				System.out.println("Please enter a valid number of human players.");
				return null;
			}
			
			numberOfAIPlayers = numberOfPlayers - numberOfHumanPlayers;
			numberOfNullPlayers = MAX_NUMBER_OF_PLAYERS - numberOfPlayers;
			
			/*
			 * prompt each player to enter their name
			 * at any stage, if player types in "quit", the game will terminate
			 * this assumes that the player does not want to be called "quit"
			 */
			for (int i = numberOfHumanPlayers - 1; i >= 0 ; i--) {
				System.out.print("Enter name of player:");
				String name = in.readLine();

				if(checkEndGame(name)){
					endGame = true;
					return null;
				}

				humanPlayerNames[i] = name;
			}

			/*
			 * this part puts the difficulty mode in the last index of the array
			 */
			if(numberOfAIPlayers > 0){
				while(promptDifficulty){
					levelOfDifficulty = promptLevelOfDifficulty();
				}			
				result[DIFFICULTY_INDEX] = levelOfDifficulty;
			} else result[DIFFICULTY_INDEX] = null;
			
			/*
			 * this part creates the string[] to be passed to Logic to create the players
			 */
			for (int i = 0; i < RESULT_SIZE - 1; i += 2) {
				if (numberOfHumanPlayers != 0) {
					result[i] = "Human";
					result[i + 1] = humanPlayerNames[numberOfHumanPlayers - 1];
					numberOfHumanPlayers--;
				} else if (numberOfAIPlayers != 0) {
					result[i] = "AI";
					result[i + 1] = null;
					numberOfAIPlayers--;
				} else if (numberOfNullPlayers != 0) {
					result[i] = "None";
					result[i + 1] = null;
				}
			}

		} catch (Exception e) {
			System.err.println(e);
		}

		/*
		 * if everything is done successfully and reaches here, players no longer need to be prompted
		 * promptPlayerStatus is set to false to exit while loop in newGame()
		 */
		promptPlayersStatus = false;
		
		return result;
	}
	
	/**
	 * 
	 */
	private void initialize(){
		gameState = logic.execute(new Message(GameState.GameMode.RESUME, null));
	}

	/**
	 * 
	 */
	private void loadGame(){
		gameState = logic.execute(new Message(GameState.GameMode.RESUME, null));
	}
	
	/**
	 * @param
	 * @return
	 * This part displays the current player's name, status of the player (points, resources)
	 * Actual printing of die result done in movePiece(),not rollDie()
	 */
	private void rollDie() {
		/*
		 * displays the current player's name
		 */
		String currentPlayerMessage = gameState.getOutput();
		System.out.println(currentPlayerMessage);
		
		/*
		 * displays current player's number of points
		 */
		int currentPoints = gameState.getCurrentPlayer().getPointsAccumulated();
		String points = "You have " + currentPoints + " points now.";
		System.out.println(points);
		
		/*
		 * displays current player's resources for them to make judgment later
		 */
		String resources = "";
		boolean needsComma = false;
		for (Player.Resource r :Player.Resource.values()){
			if (needsComma){
				resources += ", ";
				needsComma = false;
			}
			
			int amount = gameState.getCurrentPlayer().getCurrentResourceAmount(r);
			resources = resources + r.toString() + ":" + amount;
			needsComma = true;
			
		}
		System.out.println("You have these resources now: " + resources);
		
		/*
		 * informs player of die roll
		 * fake animation involved
		 */
		System.out.println("You are rolling a die now.");
		try {
			for (int i = 0; i < 15 ; i++) {
				System.out.print(".");
				Thread.sleep(100);
			} System.out.print("\n");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		gameState = logic.execute(new Message(GameState.GameMode.ROLL_DIE, null));
	}

	/**
	 * @param
	 * @result
	 * This part displays the result of die roll, possible tiles that player can move to, descriptions of tiles
	 * Also prompts player for tile that they want to move to
	 */
	private void movePiece() {
		boolean hasComma = true;
		boolean promptTileNumber = true;
		boolean error = false;
		String rollResult = gameState.getOutput();
		String errorMessage = "Please enter a valid tile number:";
		ArrayList<Integer> possibleTileNumbers = gameState.getTilesYouCanMoveTo();
		
		/*
		 * to make thread sleep so that displaying of text will not be too sudden
		 * prints result of die roll after thread awakes
		 */
		try {
			Thread.sleep(250);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		System.out.println(rollResult);

		/*
		 * prints out the tiles that player can move to, depending on die roll
		 */
		String result = "You can move to tile(s) ";
		for (int i : possibleTileNumbers) {
			if (!hasComma) {
				result = result + ", ";
			}
			result = result + i;
			hasComma = false;
		}
		result = result + ".";
		System.out.println(result);
		
		try {
			Thread.sleep(250);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		/*
		 * prints out descriptions of tiles that player can move to
		 */
		System.out.println("Here are the tile descriptions:");
		for (int i : possibleTileNumbers){
			String story = gameState.getTileManager().squareToTile(i).getNameOfTile();
			System.out.println("Tile " + i + " gives you " + story + ".");
		}

		/*
		 * prompts players for the tile that they want to move to		
		 */
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		System.out.print("Which tile do you want to move to?");

		while (promptTileNumber) {
			try {
				String number = in.readLine();

				/*
				 *checks if player enters "quit" to terminate the game
				 *asks if player wants to save game. if yes,prompt for game name and exit game
				 */
				if(checkEndGame(number)){
					quitSaveGame();
					return;
				}
				
				if(checkSaveGame(number)){
					promptToSaveGame();
					return;
				}

				/*
				 * checks if input entered is an integer
				 * if it is an integer, check if the tile number entered is valid (i.e. if player can move to that tile)
				 */
				if (isInteger(number)) {
					int tileNumber = Integer.parseInt(number);
					if (possibleTileNumbers.contains(tileNumber)) {
						String[] args = new String[1];
						args[0] = "" + tileNumber;
						promptTileNumber = false;
						System.out.println("You have moved to tile " + tileNumber + ".");
						gameState = logic.execute(new Message(GameState.GameMode.MOVE_PIECE, args));
					} else
						error = true;
				} else
					error = true;
				/*
				 * if there is error involved in the input entered, print the error message and prompt again
				 */
				if (error) {
					System.out.print(errorMessage);
					error = false;
				}
			} catch (Exception e) {
				System.err.println(e);
			}
		}
	}

	/**
	 * @param
	 * @return
	 * This part displays the amount of action points left after moving to the tile
	 * Also displays the action of the tile
	 */
	private void tileResults() {
		int actionPoints = gameState.getMovementActivityPoints();
		String tileStory = gameState.getOutput();
		System.out.println("You have " + actionPoints + " action points left.");
		System.out.println(tileStory);
		gameState = logic.execute(new Message(GameState.GameMode.TILE_RESULTS, null));
	}

	/**
	 * @param
	 * @return
	 * This part specifies the end of current player's turn
	 */
	private void endTurn() {
		String endTurnMessage = gameState.getOutput();
		System.out.println(endTurnMessage);
		System.out.println("--------------------------------------------");
		gameState = logic.execute(new Message(GameState.GameMode.END_TURN, null));
	}

	/**
	 * @param
	 * @return
	 * This part prompts the players for the building that they want to build after reaching their homebases
	 */
	private void build() {
		ArrayList<Building> buildingsYouCanBuild = gameState.getBuildingsYouCanBuild();
		
		/*
		 * if there are no buildings that the player can build with current amount of resources, proceed to next gamestate
		 */
		if (buildingsYouCanBuild.size() == 0){
			System.out.println("You do not have enough resources to build anything. Work harder!");
			gameState = logic.execute(new Message (GameState.GameMode.BUILD, null));
			return;
		}

		/*
		 * creates an array with the names of the buildings the player can build
		 */
		ArrayList<String> validBuildingNames = new ArrayList<String>();
		for (Building b : buildingsYouCanBuild){
			validBuildingNames.add(b.getName());
		}

		/*
		 * prints out all available buildings and the resources that the buildings require
		 * each set of building and its resources is an entry into buildingNameAndRequiredResources
		 */
		System.out.println("You are at your homebase now. Here is a list of buildings and the resources that they need.");
		ArrayList<String> buildingNameAndRequiredResources = new ArrayList<String>();
		ArrayList<Building> allBuildingsAvailableForBuilding = gameState.getAllBuildingsAvailableForBuilding(); 
		for (Building building : allBuildingsAvailableForBuilding){
			String output = building.getName() + " : ";
			Boolean needsComma = false;
			for (BuildingRequirement r : building.getBuildingRequirementList()){
				if (needsComma){
					output += ", ";
					needsComma = false;
				}
				output += r.getResources() + " " + r.getAmount();
				needsComma = true;
			}
			buildingNameAndRequiredResources.add(output);
		}
		for (String s : buildingNameAndRequiredResources) {
			System.out.println(s);
		}
		
		/*
		 * prints out the names of the buildings that player can build
		 */
		System.out.println("Here are the buildings that you can build with your resources:");
		boolean needsComma = false;
		String buildingsOutput = "";
		for (Building b : buildingsYouCanBuild) {
			if (needsComma) {
				buildingsOutput += ", ";
				needsComma = false;
			}
			buildingsOutput += b.getName();
			needsComma = true;
		}
		System.out.println(buildingsOutput);
		
		/*
		 * prompts player for the building that he wants to build
		 */
		System.out.print("Which building do you want to build?");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

		boolean contains = false;
		String building = new String();
		while(!contains){
			try {
				building = in.readLine();

				/*
				 * checks if player entered "quit" to terminate the game
				 */
				if(checkEndGame(building)){
					quitSaveGame();
					return;
				}
				
				if(checkSaveGame(building)){
					promptToSaveGame();
					return;
				}

				/*
				 * checks if the name entered is valid (i.e. player has enough resources to build it)
				 * if not, prompt player to enter a valid name
				 */
				for (String name : validBuildingNames){
					if (name.equals(building)){
						contains = true;
					}
				}
				if (!contains){
					System.out.print("Please enter a valid building name.");
				}
			} catch (Exception e) {
				System.err.println(e);
			}
		}

		String[] result = {building};
		gameState = logic.execute(new Message (GameState.GameMode.BUILD, result));

	}

	private void endGame(){
		String output = gameState.getOutput();
		System.out.print(output);
		endGame = true;
	}
	
	/**
	 * This part prompts for the level of difficulty for AI.
	 * @return String levelOfDifficulty for AI
	 */
	private String promptLevelOfDifficulty(){
		String result = "";
		System.out.print("Select the difficulty level of AI.(easy/normal/hard):");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try{
			String difficulty = in.readLine();
			 if ((!difficulty.equals("easy")) && (!difficulty.equals("normal") && (!difficulty.equals("hard")))){
				 System.out.println("Please enter 'easy', 'normal' or 'hard'.");
				 return null;
			 }
			 if (difficulty.equals("easy")){
				 result = "EASY";
			 } else if (difficulty.equals("normal")){
				 result = "NORMAL";
			 } else if (difficulty.equals("hard")){
				 result = "HARD";
			 }
		} catch (Exception e) {
			System.err.println(e);
		} 
		promptDifficulty = false;
		return result;
	}
	
	/**
	 * This part prints out the message that the game is saved, and to resume the game from the previous state.
	 */
	private void saveGame(){
		System.out.print("Game is being saved now");
		try {
			for (int i = 0; i < 15 ; i++) {
				System.out.print(".");
				Thread.sleep(100);
			} System.out.print("\n");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		String savedGameMessage = gameState.getOutput();
		System.out.println(savedGameMessage);
		gameState = logic.execute(new Message(GameState.GameMode.RESUME, null));
	}
	
	/**
	 * This part prints the error message if there are any errors e.g. in loading a file, and resumes the game in its previous state.
	 */
	private void error(){
		String output = gameState.getOutput();
		System.out.println(output);
		gameState = logic.execute(new Message(GameState.GameMode.RESUME, null));
	}
	
	/**
	 * Used to save the game and interact with Logic if player decides to quit the game and save it
	 */
	private void quitSaveGame(){
		boolean saveGame = false;
		String gameName = "";
		endGame = true;
		while(promptSaveGame){
			saveGame = promptSaveGame();
		}	
		promptSaveGame = true;
		if (saveGame){
			gameName = promptSaveGameName();
			String[] messageGameName = new String[1];
			messageGameName[0] = gameName;
			gameState = logic.execute(new Message(GameState.GameMode.SAVE_GAME, messageGameName));
		}
	}
	
	/**
	 * Used to save the game and interact with Logic if player decides to save the game at any time
	 */
	private void promptToSaveGame(){
		boolean saveGame = false;
		String gameName = "";
		while(promptSaveGame){
			saveGame = promptSaveGame();
		}	
		promptSaveGame = true;
		if (saveGame){
			gameName = promptSaveGameName();
			String workingDir = System.getProperty("user.dir");
			String absolutePathOfSaveGame = workingDir + "\\" + gameName;
			
			String[] messageGameName = new String[1];
			messageGameName[0] = absolutePathOfSaveGame;
			gameState = logic.execute(new Message(GameState.GameMode.SAVE_GAME, messageGameName));
		}
	}
	
	/**
	 * Used in quitSaveGame and promptToSaveGame to ask player if he wants to save the game
	 * @return
	 */
	private boolean promptSaveGame(){
		boolean saveGame= false;
		
		System.out.print("Do you want to save this game? (yes/no)");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try{
			String playerAnswer = in.readLine();
			if (playerAnswer.equals("yes")){
				saveGame = true;
			}	
			
			/*
			 * false is not used in this case
			 */
			if((!playerAnswer.equals("yes")) && (!playerAnswer.equals("no"))){
					System.out.print("Please enter 'yes' or 'no'.");
					return false;
			}
			
		} catch (Exception e) {
			System.err.println(e);
		}
		promptSaveGame = false;
		return saveGame;
	}
		
	/**
	 * Used in quitSaveGame and promptToSaveGame to get name that player wants to save game as
	 * @return
	 */
	private String promptSaveGameName(){
		String saveGameName = "";
		
		System.out.print("Save game. What name?");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try{
			saveGameName = in.readLine();		
		} catch (Exception e) {
			System.err.println(e);
		}		
		return saveGameName;
	}
		
	/**
	 * Used in newGame() to get name of saved game file that player wants to revert to
	 * @return
	 */
	private String promptGameName(){
	String saveGameName = "";
		
		System.out.print("What is the name of the saved game?");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try{
			saveGameName = in.readLine();		
		} catch (Exception e) {
			System.err.println(e);
		}
		
		return saveGameName;
	}

	private void printExitMessage() {
		System.out.println("Thank you for playing Builders!");
	}

	private boolean isInteger(String string) {
		try {
			Integer.valueOf(string);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	private boolean checkEndGame(String s){
		return s.equals("quit");		
	}
	
	private boolean checkSaveGame(String s){
		return s.equals("save game");
	}

	public static void main(String[] args) {
		TextUI ui = new TextUI();
		ui.initialise();
		ui.interactWithUser();
	}
}
